iT邦幫忙

2023 iThome 鐵人賽

DAY 9
0
Modern Web

TypeScript 啟動!系列 第 9

[Day 09] TypeScript 函式/函數 II

  • 分享至 

  • xImage
  •  

昨天我們介紹了函式的基本結構與參數等等,接下來要來認識函式的多個樣貌了,我會逐一的介紹這些函式~

產生器函式(generator function)

簡稱產生器(generator),是產生一組值的一種便利方式。而且它有一種獨特的特性,惰性(lazy)也就是說,只有要求產生器做事情的時候它才願意做事。也能產生無限串列( infinite list )

產生器函式:只有使用者要求時,才會計劃出下個數值。使用方式也很簡單,加上一個「*」就可以了。

function* fibbGenerator() { // No.1
    let i = 0;
    let j = 1;
    while(true) {
        yield i;  // No.2
        [i, j] = [j, i + j]; // NO.3
    }
}

let fibb = fibbGenerator();
console.log(fibb.next()); // { value: 0, done: false }
console.log(fibb.next()); // { value: 1, done: false }
console.log(fibb.next()); // { value: 1, done: false }
console.log(fibb.next()); // { value: 2, done: false }

  1. No.1:函式名稱前的星號(*)可以使該函式變成一個產生器( generator ),呼叫一個產生器回傳一個可迭代的迭代器(iterable iterator)
  2. No.2:產生器(genertor)使用「 yield 」關鍵字來產出( yield )值。例如當使用者向產生器要求下一個值(例如透過 next() ),yield 會送出一個結果給使用者,並暫停執行。直到下一次的請求。換句話說 while(true) 無窮迴圈不會即刻永遠執行,然後直接當掉。
  3. No.3:為了計算下個數字,我們重新指定 i, j 的數值, i = j 和 j = i + j。

如果要宣告這是一個產生器,可以使用 IterableIterator

迭代器( iterators )

產生器式產生一連串值的一種方式,那怎麼一次一次的消耗它們,就得靠迭代器一代一代的消耗。聽起來有專業術語,我們先來定義並取例子吧。

Iterable(可迭代的):含有一個叫做 Symbol.iterator 的任何物件,其值是會回傳一個迭代器的函式。
Iterator(迭代器):定義有一個 next() 的任何物件,它會回傳含有 value 和 done 的一個物件。

呼叫特徵式( call signature )

從這裡開始,就來正式來學習怎麼表示這些函式的型別了。而首先就是先看看之前的常見範例。

function add(a: number, b: number): number {
		return a + b;
}

請問 add 函式的型別為何呢? Function ?

對的,就像 Object 描述著所有的物件,Function 也描述著所有的函式,但我們還是得替他做一些定義型別的部分吧。

在 TypeScript 中,他會將 add 函式的型別推論成, (a: number, b:number) ⇒ number ,這就是用於函式型別的語法,稱之為呼叫特徵式( call signature )或是 型別特徵式( type signature ),可能會更貼近。

當然你可能會覺得他很像箭頭函式( arrow function ),這是 TypeScript 刻意的,這也是定型這個型別的語法。

呼叫特徵式只有型別層次( type-level )的程式碼,換句話說就是僅有型別沒有值。

型別層次 v.s. 值層次程式碼:基本上就是今天它是有效的 JavaScript 程式碼,那他就是值層次的。

說了那麼多,還不然先上一個範例吧~

// 建立一個呼叫特徵式的型別,logFunction
type LogFunction = (message: string) => void; // void 是不回傳唷。 // No.1

// 開始建立一個函式,是屬於 logFunction 型別
let logFunction: LogFunction = (message) => { // No.2
    console.log(message); // 不回傳,直接印出。
}
  1. No.1:宣告型別 LogFunction 的函式。
  2. No.2:參數 message 就不用特別註記了,因為我們已經透過 No.1 去註記型別。

情境式定型( Contextual Typing )

// 建立一個呼叫特徵式的型別,logFunction
type LogFunction = (message: string) => void; // void 是不回傳唷。 // No.1

// 開始建立一個函式,是屬於 logFunction 型別
let logFunction: LogFunction = (message) => { // No.2
    console.log(message); // 不回傳,直接印出。
}

用一樣的例子,當我們已經在 LogFunction 這邊(No.1)宣告了,就代表 TypeScript 能在(No.2)的 message 去推理出是一個 string 型別。 而這個 TypeScript 自己推論的功能,就是情境式定型( Contextual Typing )。

簡單 V.S. 完整。

之前的範例都是使用簡單版本的呼叫特徵式,當然也有完整版本;那有什麼差別?只有語法上的差異而已,但是複雜版的會有一些特別的功能,在下面會介紹到。

簡單版呼叫特徵式

type Log = (message: string) => void

完整版呼叫特徵式

type Log = {
		(message: string): void
}

完整版的會有什麼好處嗎?當然有的,其中第一個就是可以重載( overloading );假設想要設計預訂去日本的單程與來回機票的函式,我們可以這樣寫。

type Book = {
    (from: Date, Destination: string): Booking,
    (from: Date, to:Date, Destination: string): Booking,
}

let book: Book = (from, destination) => {
    return true;
}

這邊就有介面( interface )的感覺在這邊,後續我們會開始介紹類別與介面~


上一篇
[Day 08] TypeScript 函式/函數I
下一篇
[Day 10] TypeScript 類別與介面
系列文
TypeScript 啟動!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言